package cashpointer.core.support;

import au.id.jericho.lib.html.Element;
import au.id.jericho.lib.html.Source;
import cashpointer.core.model.Address;
import cashpointer.core.model.GeoPoint;
import java.io.IOException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;

/**
 * Klasa wspomagajaca ustalic polozenie geograficzne bankomatu. Pomozenie jest
 * ustalana na podstawie adresu dostarczonego w argumencie.
 * @author wojciech topolski
 * @version 2009-04-06
 */
public class GeoPointFinderUtil implements GeoPointFinder {

    // Logger.
    private static Log log = LogFactory.getLog(GeoPointFinderUtil.class);

    // No errors occurred; the address was successfully parsed and its geocode was returned.
    private static String G_GEO_SUCCESS = "200";
    private static String G_GEO_SUCCESS_DESC = "No errors occurred; the address was successfully parsed and its geocode was returned.";

    // A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is unknown.
    private static String G_GEO_SERVER_ERROR = "500";
    private static String G_GEO_SERVER_ERROR_DESC = "A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is unknown.";

    // An empty address was specified in the HTTP q parameter.
    private static String G_GEO_MISSING_QUERY = "601";
    private static String G_GEO_MISSING_QUERY_DESC = "An empty address was specified in the HTTP q parameter.";

    // No corresponding geographic location could be found for the specified address, possibly because the address is relatively new, or because it may be incorrect.
    private static String G_GEO_UNKNOWN_ADDRESS = "602";
    private static String G_GEO_UNKNOWN_ADDRESS_DESC = "No corresponding geographic location could be found for the specified address, possibly because the address is relatively new, or because it may be incorrect";

    // The geocode for the given address or the route for the given directions query cannot be returned due to legal or contractual reasons.
    private static String G_GEO_UNAVAILABLE_ADDRESS = "603";
    private static String G_GEO_UNAVAILABLE_ADDRESS_DESC = "The geocode for the given address or the route for the given directions query cannot be returned due to legal or contractual reasons.";

    // The given key is either invalid or does not match the domain for which it was given.
    private static String G_GEO_BAD_KEY = "610";
    private static String G_GEO_BAD_KEY_DESC = "The given key is either invalid or does not match the domain for which it was given.";

    // The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly.
    private static String G_GEO_TOO_MANY_QUERIES = "620";
    private static String G_GEO_TOO_MANY_QUERIES_DESC = "The given key has gone over the requests limit in the 24 hour period or has submitted too many requests in too short a period of time. If you're sending multiple requests in parallel or in a tight loop, use a timer or pause in your code to make sure you don't send the requests too quickly.";

    /**
     * Funkcja ustala polozenie geograficzne podanego adresu.
     * @param address Address
     * @param ctx ApplicationContext
     * @return GeoPoint
     */
    public GeoPoint findByAddress(Address address, ApplicationContext ctx) {
        // Pobranie propertyStoreBean.
        PropertyStore propertyStoreBean = (PropertyStore) ctx.getBean("propertyStoreBean");

        // Klucz autoryzacyjny
        String key = propertyStoreBean.getPropertyByName("net.googleKey");

        // Koordynaty do zwrocenia
        GeoPoint gp = null;

        // Budowanie zapytania URL.
        StringBuilder urlAddress = new StringBuilder();
        urlAddress.append("http://maps.google.pl/maps/geo?q=");
        urlAddress.append("Polska, ");
        urlAddress.append(address.getRegion());
        urlAddress.append(", ");
        urlAddress.append(address.getCity());
        urlAddress.append(", ");
        urlAddress.append(address.getStreet());
        urlAddress.append("&output=xml&oe=utf8&sensor=true&key=");
        urlAddress.append(key);

        // Budowanie zapytania bez ulicy, dla bankomatow ktorych nie mozna zlokalizowac.
        StringBuilder urlAddressWithoutStreet = new StringBuilder();
        urlAddressWithoutStreet.append("http://maps.google.pl/maps/geo?q=");
        urlAddressWithoutStreet.append("Polska, ");
        urlAddressWithoutStreet.append(address.getRegion());
        urlAddressWithoutStreet.append(", ");
        urlAddressWithoutStreet.append(address.getCity());
        urlAddressWithoutStreet.append("&output=xml&oe=utf8&sensor=true&key=");
        urlAddressWithoutStreet.append(key);

        // Zapytanie URL
        String URLAddress = urlAddress.toString().replaceAll(" ", "+");
        String URLAddressWithoutStreet = urlAddressWithoutStreet.toString().replaceAll(" ", "+");
        Boolean secondURLUsed = false;

        if (log.isDebugEnabled()) {
            log.debug("[GeoPointFinderUtil::findByAddress(String address)]" + URLAddress);
        }

        Boolean flag = true;
        int queryNumber = 0;
        // Maxymalnie 10 prob ustalenia adresu
        while (flag && queryNumber < 10) {
            // Wykonanie zapytania i pobranie odpowiedzi.
            Source source = null;
            try {
                source = new Source(new URL(URLAddress));
            } catch (IOException ioe) {
                if (log.isErrorEnabled()) {
                    log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad.", ioe);
                }
            }

            // Przetwarzanie odpowiedzi.
            if (source != null) {
                if (log.isDebugEnabled()) {
                    log.debug("[CoordinatesFinderUtil::findByAddress(String address)] Zawartosc source: " + source.toString());
                }

                String content = null;

                // Pobranie koordynatow i zbudowanie obiektu GeoPoint.
                List code = source.findAllElements("code");
                if (code.size() == 1) {
                    Iterator i = code.iterator();
                    Element codeElement = (Element) i.next();
                    content = codeElement.getContent().toString();

                    if (G_GEO_SUCCESS.equalsIgnoreCase(content)) {
                        if (log.isDebugEnabled()) {
                            log.debug("[CoordinatesFinderUtil::findByAddress(String address)] '" + G_GEO_SUCCESS_DESC + "' Dla: " + URLAddress);
                        }
                    }
                    else if (G_GEO_SERVER_ERROR.equalsIgnoreCase(content)) {
                        try {
                            Thread.sleep(100);

                            if (log.isErrorEnabled()) {
                                log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!! '" + G_GEO_SERVER_ERROR_DESC + "' Dla: " + URLAddress);
                            }
                        }
                        catch (InterruptedException ie) {
                            if (log.isErrorEnabled()) {
                                log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!!", ie);
                            }
                        }
                        finally {
                            queryNumber++;
                            continue;
                        }
                    }
                    else if (G_GEO_MISSING_QUERY.equalsIgnoreCase(content)) {
                        if (log.isErrorEnabled()) {
                            log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!! '" + G_GEO_MISSING_QUERY_DESC + "' Dla: " + URLAddress);
                        }
                        break;
                    }
                    else if (G_GEO_UNKNOWN_ADDRESS.equalsIgnoreCase(content)) {
                        if (log.isErrorEnabled()) {
                            log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!! '" + G_GEO_UNKNOWN_ADDRESS_DESC + "' Dla: " + URLAddress);
                        }

                        URLAddress = URLAddressWithoutStreet;
                        queryNumber++;
                        secondURLUsed = true;
                        continue;
                    }
                    else if (G_GEO_UNAVAILABLE_ADDRESS.equalsIgnoreCase(content)) {
                        if (log.isErrorEnabled()) {
                            log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!! '" + G_GEO_UNAVAILABLE_ADDRESS_DESC + "' Dla: " + URLAddress);
                        }

                        URLAddress = URLAddressWithoutStreet;
                        queryNumber++;
                        secondURLUsed = true;
                        continue;
                    }
                    else if (G_GEO_BAD_KEY.equalsIgnoreCase(content)) {
                        if (log.isErrorEnabled()) {
                            log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!! '" + G_GEO_BAD_KEY_DESC + "' Dla: " + URLAddress);
                        }
                        break;
                    }
                    else if (G_GEO_TOO_MANY_QUERIES.equalsIgnoreCase(content)) {
                        try {
                            Thread.sleep(100);

                            if (log.isErrorEnabled()) {
                                log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!! '" + G_GEO_TOO_MANY_QUERIES_DESC + "' Dla: " + URLAddress);
                            }
                        }
                        catch (InterruptedException ie) {
                            if (log.isErrorEnabled()) {
                                log.error("[CoordinatesFinderUtil::findByAddress(String address)] Blad!!!", ie);
                            }
                        }
                        finally {
                            queryNumber++;
                            continue;
                        }
                    }
                    else {
                        if (log.isErrorEnabled()) {
                            log.error("[CoordinatesFinderUtil::findByAddress(String address)] Nie rozpoznano kodu!!! Dla: " + URLAddress);
                        }
                        break;
                    }
                }
                // Powazny blad!!!
                else {
                    if (log.isErrorEnabled()) {
                        log.error("[CoordinatesFinderUtil::findByAddress(String address)] Brak tagu code w: " + source.toString() + " dla: " + URLAddress);
                    }
                    break;
                }

                // Pobranie koordynatow i zbudowanie obiektu GeoPoint.
                List coordinates = source.findAllElements("coordinates");
                content = new String("");

                for (Iterator i = coordinates.iterator(); i.hasNext();) {
                    Element element = (Element) i.next();
                    content = element.getContent().toString();
                }

                if (content != null) {
                    String[] values = content.split(",");

                    // Polozenie geograficzne: (Szerokosc geograficzna, Dlugosc geograficzna)
                    gp = new GeoPoint(values[1], values[0]);
                }

                // Pobranie nazwy powiatu i dodanie jej do adresu.
                List areas = source.findAllElements("SubAdministrativeAreaName");
                content = new String("");

                for (Iterator i = areas.iterator(); i.hasNext();) {
                    Element element = (Element) i.next();
                    content = element.getContent().toString();
                }

                // Udalo sie ustalis adres. Koniec petli. Sukces.
                if (content != null) {
                    address.setCounty(content);

                    // Dodanie informacji o uzyciu przyblizonego polozenia
                    if (secondURLUsed) {
                        address.setInfo("Uwaga! Polozenie geografine jest przybliżone! " + address.getInfo());
                    }

                    break;
                }
            }
            else {
                if (log.isErrorEnabled()) {
                    log.error("[CoordinatesFinderUtil::findByAddress(String address)] source jest null.");
                }
            }
        }

        return gp;
    }
}
